/**
 * libpsd - Photoshop file formats (*.psd) decode library
 * Copyright (C) 2004-2007 Graphest Software.
 *
 * libpsd is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: thumbnail.c, created by Patrick in 2006.05.31, libpsd@graphest.com Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libpsd.h"
#include "psd_config.h"
#include "psd_system.h"
#include "psd_stream.h"
#include "psd_color.h"
#include "psd_math.h"

#ifdef PSD_INCLUDE_LIBJPEG
#include <setjmp.h>
#include "jpeglib.h"


/* we are a "source manager" as far as libjpeg is concerned */
#define JPEG_PROG_BUF_SIZE 32768

typedef struct {
    struct jpeg_source_mgr pub;                    /* public fields */

    JOCTET buffer[JPEG_PROG_BUF_SIZE];            /* start of buffer */
    long  skip_next;                            /* number of bytes to skip next read */
} my_source_mgr;

typedef my_source_mgr * my_src_ptr;

/* error handler data */
struct error_handler_data {
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};

/* progressive loader context */
typedef struct {
    psd_uchar *                        pixels;
    psd_uchar *                        dptr;   /* current position in image_data */
    psd_int                            width;
    psd_int                            height;
    psd_int                            number_channels;
    psd_int                            row_stride;

    boolean                            did_prescan;/* are we in image data yet? */
    boolean                            got_header; /* have we loaded jpeg header? */
    boolean                            src_initialized;/* TRUE when jpeg lib initialized */
    boolean                         in_output;  /* did we get suspended in an output pass? */
    struct jpeg_decompress_struct     cinfo;
    struct error_handler_data         jerr;
} JpegProgContext;


/**** Progressive image loading handling *****/

psd_static void fatal_error_handler (j_common_ptr cinfo)
{
    /* FIXME:
     * We should somehow signal what error occurred to the caller so the
     * caller can handle the error message */
    struct error_handler_data *errmgr;

    errmgr = (struct error_handler_data *) cinfo->err;
    cinfo->err->output_message (cinfo);
    longjmp (errmgr->setjmp_buffer, 1);
}

/* these routines required because we are acting as a source manager for */
/* libjpeg. */
psd_static void init_source (j_decompress_ptr cinfo)
{
    my_src_ptr src = (my_src_ptr) cinfo->src;
    src->skip_next = 0;
}

/* for progressive loading (called "I/O Suspension" by libjpeg docs) */
/* we do nothing except return "FALSE"                               */
psd_static boolean fill_input_buffer (j_decompress_ptr cinfo)
{
    return FALSE;
}

psd_static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
    my_src_ptr src = (my_src_ptr) cinfo->src;
    long   num_can_do;

    /* move as far as we can into current buffer */
    /* then set skip_next to catch the rest      */
    if (num_bytes > 0) {
        num_can_do = PSD_MIN(src->pub.bytes_in_buffer, num_bytes);
        src->pub.next_input_byte += num_can_do;
        src->pub.bytes_in_buffer -= num_can_do;

        src->skip_next = num_bytes - num_can_do;
    }
}

psd_static void term_source (j_decompress_ptr cinfo)
{
    /* XXXX - probably should scream something has happened */
}

/* explode gray image data from jpeg library into rgb components in pixbuf */
psd_static void explode_gray_into_buf (struct jpeg_decompress_struct *cinfo, psd_uchar **lines) 
{
    psd_int i, j;
    psd_int w;

    if(! (cinfo->output_components == 1))
        return;

    /* Expand grey->colour.  Expand from the end of the
     * memory down, so we can use the same buffer.
     */
    w = cinfo->image_width;
    for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) {
        psd_uchar *from, *to;
        
        from = lines[i] + w - 1;
        to = lines[i] + (w - 1) * 3;
        for (j = w - 1; j >= 0; j--) {
            to[0] = from[0];
            to[1] = from[0];
            to[2] = from[0];
            to -= 3;
            from--;
        }
    }
}

psd_static void convert_cmyk_to_rgb (struct jpeg_decompress_struct *cinfo, psd_uchar **lines) 
{
    psd_int i, j;

    if(! (cinfo->output_components == 4))
        return;

    for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) {
        psd_uchar *p;
        
        p = lines[i];
        for (j = 0; j < cinfo->image_width; j++) {
            psd_int c, m, y, k;
            c = p[0];
            m = p[1];
            y = p[2];
            k = p[3];
            if (cinfo->saw_Adobe_marker) {
                p[0] = k*c / 255;
                p[1] = k*m / 255;
                p[2] = k*y / 255;
            }
            else {
                p[0] = (255 - k)*(255 - c) / 255;
                p[1] = (255 - k)*(255 - m) / 255;
                p[2] = (255 - k)*(255 - y) / 255;
            }
            p[3] = 255;
            p += 4;
        }
    }
}

psd_static JpegProgContext * psd_jpeg_image_begin_load(void)
{
    JpegProgContext * jpeg_context;
    my_source_mgr   *src;

    jpeg_context = (JpegProgContext *)psd_malloc(sizeof(JpegProgContext));
    if(jpeg_context == NULL)
        return NULL;
    memset(jpeg_context, 0, sizeof(JpegProgContext));

    jpeg_context->got_header = FALSE;
    jpeg_context->did_prescan = FALSE;
    jpeg_context->src_initialized = FALSE;
    jpeg_context->in_output = FALSE;
    
    /* create libjpeg structures */
    jpeg_create_decompress (&jpeg_context->cinfo);

    jpeg_context->cinfo.src = (struct jpeg_source_mgr *)psd_malloc(sizeof(my_source_mgr));
    src = (my_src_ptr) jpeg_context->cinfo.src;

    jpeg_context->cinfo.err = jpeg_std_error (&jpeg_context->jerr.pub);
    jpeg_context->jerr.pub.error_exit = fatal_error_handler;

    src = (my_src_ptr) jpeg_context->cinfo.src;
    src->pub.init_source = init_source;
    src->pub.fill_input_buffer = fill_input_buffer;
    src->pub.skip_input_data = skip_input_data;
    src->pub.resync_to_restart = jpeg_resync_to_restart;
    src->pub.term_source = term_source;
    src->pub.bytes_in_buffer = 0;
    src->pub.next_input_byte = NULL;

    return jpeg_context;
}

psd_static boolean psd_jpeg_image_load_increment(JpegProgContext * jpeg_context, psd_uchar * buffer, psd_int size)
{
    struct jpeg_decompress_struct *cinfo;
    my_src_ptr  src;
    psd_int num_left, num_copy;
    psd_int last_bytes_left;
    psd_int spinguard;
    boolean first;
    const psd_uchar * bufhd;

    src = (my_src_ptr) jpeg_context->cinfo.src;

    cinfo = &jpeg_context->cinfo;

    /* check for fatal error */
    if (setjmp (jpeg_context->jerr.setjmp_buffer)) {
        return FALSE;
    }

    /* skip over data if requested, handle psd_uint sizes cleanly */
    /* only can happen if we've already called jpeg_get_header once   */
    if (jpeg_context->src_initialized && src->skip_next) {
        if (src->skip_next > size) {
            src->skip_next -= size;
            return TRUE;
        } else {
            num_left = size - src->skip_next;
            bufhd = buffer + src->skip_next;
            src->skip_next = 0;
        }
    } else {
        num_left = size;
        bufhd = buffer;
    }

    if (num_left == 0)
        return TRUE;

    last_bytes_left = 0;
    spinguard = 0;
    first = TRUE;
    while (TRUE) {

        /* handle any data from caller we haven't processed yet */
        if (num_left > 0) {
            if(src->pub.bytes_in_buffer && 
               src->pub.next_input_byte != src->buffer)
                memmove(src->buffer, src->pub.next_input_byte,
                    src->pub.bytes_in_buffer);


            num_copy = PSD_MIN(JPEG_PROG_BUF_SIZE - src->pub.bytes_in_buffer,
                    num_left);

            memcpy(src->buffer + src->pub.bytes_in_buffer, bufhd,num_copy);
            src->pub.next_input_byte = src->buffer;
            src->pub.bytes_in_buffer += num_copy;
            bufhd += num_copy;
            num_left -= num_copy;
        } else {
        /* did anything change from last pass, if not return */
            if (first) {
                last_bytes_left = src->pub.bytes_in_buffer;
                first = FALSE;
            } else if (src->pub.bytes_in_buffer == last_bytes_left)
                spinguard++;
            else
                last_bytes_left = src->pub.bytes_in_buffer;
        }

        /* should not go through twice and not pull bytes out of buffer */
        if (spinguard > 2)
            return TRUE;

        /* try to load jpeg header */
        if (!jpeg_context->got_header) {
            psd_int rc;

            rc = jpeg_read_header (cinfo, TRUE);
            jpeg_context->src_initialized = TRUE;

            if (rc == JPEG_SUSPENDED)
                continue;

            jpeg_context->got_header = TRUE;

        } else if (!jpeg_context->did_prescan) {
            psd_int rc;
                        
            /* start decompression */
            cinfo->buffered_image = TRUE;
            rc = jpeg_start_decompress (cinfo);
            cinfo->do_fancy_upsampling = FALSE;
            cinfo->do_block_smoothing = FALSE;

            jpeg_context->number_channels = cinfo->output_components == 4 ? 4 : 3;
            jpeg_context->pixels = (psd_uchar *)psd_malloc(cinfo->image_width * 
                cinfo->image_height * jpeg_context->number_channels);
            if(jpeg_context->pixels == NULL)
                return FALSE;
            jpeg_context->width = cinfo->image_width;
            jpeg_context->height = cinfo->image_height;
            jpeg_context->row_stride = jpeg_context->width * jpeg_context->number_channels;

            /* Use pixbuf buffer to store decompressed data */
            jpeg_context->dptr = jpeg_context->pixels;

            if (rc == JPEG_SUSPENDED)
                continue;

            jpeg_context->did_prescan = TRUE;
        } else {
            /* we're decompressing so feed jpeg lib scanlines */
            psd_uchar *lines[4];
            psd_uchar **lptr;
            psd_uchar *rowptr;
            psd_int   nlines, i;

            /* keep going until we've done all passes */
            while (!jpeg_input_complete (cinfo)) {
                if (!jpeg_context->in_output) {
                    if (jpeg_start_output (cinfo, cinfo->input_scan_number)) {
                        jpeg_context->in_output = TRUE;
                        jpeg_context->dptr = jpeg_context->pixels;
                    }
                    else
                        break;
                }
                /* keep going until we've done all scanlines */
                while (cinfo->output_scanline < cinfo->output_height) {
                    lptr = lines;
                    rowptr = (psd_uchar *)jpeg_context->dptr;
                    for (i=0; i < cinfo->rec_outbuf_height; i++) {
                        *lptr++ = rowptr;
                        rowptr += jpeg_context->row_stride;
                    }
                    
                    nlines = jpeg_read_scanlines (cinfo, lines,
                                      cinfo->rec_outbuf_height);
                    if (nlines == 0)
                        break;

                    switch (cinfo->out_color_space) {
                        case JCS_GRAYSCALE:
                            explode_gray_into_buf (cinfo, lines);
                            break;
                        case JCS_RGB:
                            /* do nothing */
                            break;
                        case JCS_CMYK:
                            convert_cmyk_to_rgb (cinfo, lines);
                            break;
                        default:
                            return FALSE;
                    }

                    jpeg_context->dptr += nlines * jpeg_context->row_stride;
                }
                if (cinfo->output_scanline >= cinfo->output_height && 
                    jpeg_finish_output (cinfo))
                    jpeg_context->in_output = FALSE;
                else
                    break;
            }
            if (jpeg_input_complete (cinfo))
                /* did entire image */
                return TRUE;
            else
                continue;
        }
    }

    return TRUE;
}

psd_static void psd_jpeg_image_stop_load(JpegProgContext * jpeg_context)
{
    psd_freeif(jpeg_context->pixels);
    
    /* if we have an error? */
    if (setjmp (jpeg_context->jerr.setjmp_buffer)) {
        jpeg_destroy_decompress (&jpeg_context->cinfo);
    } else 
    {
        jpeg_finish_decompress(&jpeg_context->cinfo);
        jpeg_destroy_decompress(&jpeg_context->cinfo);
    }

    if (jpeg_context->cinfo.src) {
        my_src_ptr src = (my_src_ptr) jpeg_context->cinfo.src;
        psd_free(src);
    }

    psd_free(jpeg_context);
}

psd_status psd_thumbnail_decode_jpeg(psd_argb_color ** dst_image, psd_int compress_len, psd_context * context)
{
    psd_uchar * buffer;
    JpegProgContext * jpeg_context;
    psd_int length, i;
    psd_argb_color * image_data, * data;
    psd_uchar * pixels;

    buffer = (psd_uchar *)psd_malloc(4096);
    if(buffer == NULL)
        return psd_status_malloc_failed;

    jpeg_context = psd_jpeg_image_begin_load();
    if(jpeg_context == NULL)
    {
        psd_free(buffer);
        return psd_status_malloc_failed;
    }

    while(compress_len > 0)
    {
        length = psd_stream_get(context, buffer, PSD_MIN(4096, compress_len));
        if(length > 0)
        {
            if(psd_jpeg_image_load_increment(jpeg_context, buffer, length) == psd_false)
            {
                psd_free(buffer);
                psd_jpeg_image_stop_load(jpeg_context);
                return psd_status_thumbnail_decode_error;
            }
        }
        else if(length == 0)
        {
            psd_free(buffer);
            psd_jpeg_image_stop_load(jpeg_context);
            return psd_status_thumbnail_decode_error;
        }
        compress_len -= length;
    }

    image_data = (psd_argb_color *)psd_malloc(jpeg_context->width * jpeg_context->height * 4);
    if(image_data == NULL)
    {
        psd_free(buffer);
        psd_jpeg_image_stop_load(jpeg_context);
        return psd_status_malloc_failed;
    }
    pixels = jpeg_context->pixels;
    data = image_data;
    if(jpeg_context->number_channels == 4)
    {
        for(i = jpeg_context->width * jpeg_context->height; i --; )
        {
            *data = PSD_ARGB_TO_COLOR(*(pixels + 3), *pixels, *(pixels + 1), *(pixels + 2));
            data ++;
            pixels += 4;
        }
    }
    else
    {
        for(i = jpeg_context->width * jpeg_context->height; i --; )
        {
            *data = PSD_RGB_TO_COLOR(*pixels, *(pixels + 1), *(pixels + 2));
            data ++;
            pixels += 3;
        }
    }

    *dst_image = image_data;
    psd_free(buffer);
    psd_jpeg_image_stop_load(jpeg_context);
    
    return psd_status_done;
}

psd_status psd_thumbnail_decode_raw(psd_argb_color ** dst_image, psd_int image_len, psd_context * context)
{
    // currently, we don't support the raw format
    // since we don't meet any psd file with raw thumbnail
    psd_assert(0);
    psd_stream_get_null(context, image_len);
    
    return psd_status_done;
}

#endif
